<?php if (!defined('CORE_DIR')) die('Wrong entry point!');

/* 
 * Copyright 2010 Anton Muraviev a.k.a kuromaisu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

// =============================================================================
// Section: Error Handler
// =============================================================================
// 
// Configuration namespace:
// 	errors
// 
// Configuration options:
// 	catch_mask		-	bit mask; which errors should this library catch?
// 	critical_mask	-	bit mask; on which errors should the system halt?
// 

Aero::define('AeroErrorHandler', AS_STATIC);

/* -----------------------------------------------------------------------------
 * Class: AeroErrorHandler
 * -----------------------------------------------------------------------------
 *
 */
class AeroErrorHandler
{
	// Previous non-critical errors
	static protected $notices = array();
	static protected $plugins = array();
	
	static public function addPlugin($plugin)
	{
		if (!$plugin instanceof AeroErrorHandlerInterface)
		{
			return;
		}
		
		if (count(self::$plugins) === 0)
		{
			// Set up all handlers
			set_error_handler(array('AeroErrorHandler','handleError'), Aero::option('errors.catch_mask', E_ALL | E_STRICT));
			set_exception_handler(array('AeroErrorHandler','handleException'));
			register_shutdown_function(array('AeroErrorHandler','handleShutdown'));
		}
		
		$offset = (int)$plugin->priority(); 
		
		// Find unoccupied offset
		while(isset(self::$plugins[$offset])) $offset++;
		
		self::$plugins[$offset] = $plugin;
		ksort(self::$plugins);
	}
	
	static public function handleError($type, $message, $file, $line, $args)
	{
		$error['type'] = $type;
		$error['message'] = $message;
		$error['file'] = $file;
		$error['line'] = $line;
		$error['args'] = $args;
		
		if ($type & Aero::option('errors.critical_mask', E_ALL ^ E_NOTICE ^ E_USER_NOTICE))
		{
			$trace = debug_backtrace();
			array_shift($trace);
			self::dispatch('error', $error, $trace);
		}
		else
		{
			self::$notices[] = $error;
			self::dispatch('notice', $error);
		}
	}
	
	static public function handleShutdown()
	{
		$error = error_get_last();
		$_error = end(self::$notices);
		
		if (!is_array($error) || is_array($_error)
			&& $error['message'] === $_error['message']
			&& $error['file'] === $_error['file']
			&& $error['line'] === $_error['line'])
		{
			self::dispatch('shutdown');
		}
		else
		{
			self::dispatch('critical', $error);
		}
	}
	
	static public function handleException($e)
	{
		$error['type'] = $e->getCode();
		$error['message'] = $e->getMessage();
		$error['file'] = $e->getFile();
		$error['line'] = $e->getLine();
		
		self::dispatch('error', $error, $e->getTrace());
	}
	
	static protected function dispatch($event, $error = null, $backtrace = null)
	{
		$method = 'on'.ucfirst($event);
		
		if (!method_exists('AeroErrorHandlerInterface', $method))
		{
			return;
		}
		
		foreach (self::$plugins as $plugin)
		{
			$plugin->{$method}($error, $backtrace);
		}
	}
	
	static public function notices()
	{
		return self::$notices;
	}
	
	static public function environment()
	{
		// TODO: return current php state, i.e. $_SESSION, $_SERVER, etc.
	}
}

/*
 * Constants: Predefined plugin priorities
 */
define('ERROR_HANLDER_PLUGIN_PRIORITY_HIGH', 1000);
define('ERROR_HANLDER_PLUGIN_PRIORITY_MEDIUM', 5000);
define('ERROR_HANLDER_PLUGIN_PRIORITY_LOW', 10000);

/* -----------------------------------------------------------------------------
 * Interface: AeroErrorHandlerInterface
 * -----------------------------------------------------------------------------
 */
interface AeroErrorHandlerInterface
{
	public function priority();
	public function onError($error, $backtrace = array());
	public function onNotice($error);
	public function onShutdown();
}